
import * as babel from "@babel/core";
import * as fs from "fs";
import * as path from "path";
import { errors } from "../errors";
import { ProjectCompiler } from "../project-compiler";

const outExt = ProjectCompiler.tsOutExt;

/** 
 * 编译特定文件，并生成到制定目录 
 * @param sourcePath 源文件路径
 * @param outputPath 输出目录
 * @param projectPath 项目路径
 * */
export async function compileFile(sourcePath: string, outputPath: string, projectPath: string) {
    if (!sourcePath) throw errors.argumentNull("sourcePath");
    if (!outputPath) throw errors.argumentNull("outputPath");
    if (!projectPath) throw errors.argumentNull("projectPath");

    // if (!fs.existsSync(sourcePath)) throw errors.pathNotExists(sourcePath);
    if (!fs.existsSync(sourcePath)) {
        console.warn(`Path not exists: ${sourcePath}`);
        return;
    }

    let sourceDir = path.dirname(sourcePath);
    let babelOptions: babel.TransformOptions;
    let bablePath: string;
    //= projectPath ?
    //  ProjectCompiler.getBabelConfig(projectPath, sourceDir) : ProjectCompiler.getDefaultBabelConfig();
    if (projectPath) {
        let c = ProjectCompiler.getBabelConfig(projectPath, sourceDir);
        bablePath = c.path;
        babelOptions = c.options;
    }
    else {
        babelOptions = ProjectCompiler.getDefaultBabelConfig();
        bablePath = '';
    }

    babelOptions.filename = sourcePath;
    babelOptions.code = false;
    babelOptions.ast = true;


    // let fileResult = babel.transformFileSync(sourcePath, {
    //     filename: sourcePath, code: false, ast: true, plugins,
    //     presets
    // });
    let fileResult = babel.transformFileSync(sourcePath, babelOptions);
    if (!fileResult)
        throw errors.compileError(sourcePath);

    let ast = fileResult.ast;
    if (!ast)
        throw errors.compileError(sourcePath);

    new ImportPathRewrite(sourcePath, ast);
    let r = babel.transformFromAstSync(ast, undefined, {
        filename: sourcePath, plugins: babelOptions.plugins,
        presets: babelOptions.presets, sourceMaps: babelOptions.sourceMaps
    });
    if (!r || r.code == null)
        throw errors.compileError(sourcePath);

    let ext = extname(sourcePath);
    let outExt = fileOutExt(sourcePath);
    let targetPath = path.join(outputPath, path.basename(sourcePath).replace(ext, outExt));//sourcePath.replace(new RegExp(ext + "$"), outExt).replace(path.dirname(sourcePath), outDir);
    let outDirPath = path.resolve(targetPath, "..");

    let mapPath = targetPath + ".map";
    if (r.map) {
        r.map.file = path.basename(targetPath);
        r.map.sources = [path.relative(outputPath, sourcePath)];

        if (!fs.existsSync(outDirPath))
            fs.mkdirSync(outDirPath, { recursive: true });

        fs.writeFileSync(mapPath, JSON.stringify(r.map));
        r.code += `\n//babel file path = ${bablePath}`;
        r.code += "\n//# sourceMappingURL=" + path.basename(mapPath);
    }
    else if (fs.existsSync(mapPath)) {
        fs.rmSync(targetPath);
    }

    if (!fs.existsSync(outDirPath))
        fs.mkdirSync(outDirPath, { recursive: true });

    fs.writeFileSync(targetPath, r.code);

}

function extname(file: string) {
    // let ext = /\.[a-zA-Z]+/.exec(file)?.[0] || '';
    let ext = path.extname(file);
    return ext;
}

/** 
 * 获取源文件所对应生成文件的扩展名 
 * @param file 源文件名
 * */
function fileOutExt(file: string) {
    let ext = extname(file);
    if (ext === ".ts")
        return outExt;

    if (ext === ".tsx")
        return outExt;

    return ext;
}


class ImportPathRewrite {
    constructor(private filePath: string, node: babel.types.File) {
        this.traverse(node);
    }

    private traverse(node: babel.types.Node) {
        switch (node.type) {
            case "Program":
                node.body.forEach(c => this.traverse(c));
                break;
            case "File":
                this.traverse(node.program);
                break;
            case "ImportDeclaration":
                if (node.source.type === "StringLiteral" && node.source.value.startsWith(".")) {
                    let ext = extname(node.source.value);
                    if (ext != outExt) {
                        let dir = path.dirname(this.filePath);
                        let fullPath = path.join(dir, node.source.value);
                        console.log(`ImportDeclaration: ${fullPath} -> ${ext}`)
                        if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) {
                            node.source.value = node.source.value + "/index";
                        }
                        node.source.value += outExt;
                    }
                }
                break;

        }
    }

}
